﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using GmailMetroNotifier.Client.Common;
using GmailMetroNotifier.Client.UI;

namespace GmailMetroNotifier.Client.Business
{
    /// <summary>
    /// Provides a simple <see cref="IGmailManager"/> implementation.
    /// </summary>
    public class GmailManager : IGmailManager
    {
        /// <summary>
        /// The event raised when the authentication fails.
        /// </summary>
        public event EventHandler<AuthenticationFailedEventArgs> AuthenticationFailed;

        /// <summary>
        /// The event raised when the authentication succeeds.
        /// </summary>
        public event EventHandler<AuthenticationSucceededEventArgs> AuthenticationSucceeded;

        private IAtomFeedProvider _atomFeedProvider = null;

        private Dictionary<string, DateTime> _lastModifiedDates =
            new Dictionary<string, DateTime>();

        /// <summary>
        /// Initializes a new instance of the class using a new instance of <see cref="AtomFeedProvider"/>.
        /// </summary>
        public GmailManager()
            : this(new AtomFeedProvider())
        {

        }

        /// <summary>
        /// Initializes a new instance of the class using the given <see cref="IAtomFeedProvider"/>.
        /// </summary>
        /// <param name="provider">The <see cref="IAtomFeedProvider"/> to be used for retrieving the Atom feeds.</param>
        public GmailManager(IAtomFeedProvider provider)
        {
            this._atomFeedProvider = provider;
        }

        /// <summary>
        /// Gets the account status using the given credential, retrying <see cref="Constants.MaxAuthenticationRetries"/> times.
        /// </summary>
        /// <param name="credential">The credential to be used for retrieving the account status.</param>
        /// <returns>The account status, or null if it cannot be retrieved.</returns>
        /// <exception cref="ArgumentNullException">Thrown when credential is null.</exception>
        public AccountStatus GetAccountStatus(NetworkCredential credential)
        {
            return this.GetAccountStatus(credential, Constants.MaxAuthenticationRetries);
        }

        /// <summary>
        /// Gets the account status using the given credential, retrying the specified times.
        /// </summary>
        /// <param name="credential">The credential to be used for retrieving the account status.</param>
        /// <param name="maxAuthenticationRetries">Max number of authentication retries if the initial attempt fails.</param>
        /// <returns>The account status, or null if it cannot be retrieved.</returns>
        /// <exception cref="ArgumentNullException">Thrown when credential is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException">Thrown when the specified retries is less than zero.</exception>
        public AccountStatus GetAccountStatus(NetworkCredential credential, int maxAuthenticationRetries)
        {
            if (credential == null)
            {
                throw new ArgumentNullException("credential");
            }

            if (maxAuthenticationRetries < 0)
            {
                throw new ArgumentOutOfRangeException("maxAuthenticationRetries");
            }

            AccountStatus status = null;

            bool retry = maxAuthenticationRetries > 0;

            string atomFeed = null;

            for (int retriesCount = -1; retriesCount < maxAuthenticationRetries && retry; retriesCount++)
            {
                try
                {
                    atomFeed = this._atomFeedProvider.GetAtomFeed(credential);

                    // If we reach this point, credentials are valid
                    retry = false;

                    AuthenticationSucceededEventArgs args = new AuthenticationSucceededEventArgs(credential.UserName, credential.Password);

                    this.OnAuthenticationSucceeded(args);
                }
                catch (AuthenticationFailedException exception)
                {
                    AuthenticationFailedEventArgs args = new AuthenticationFailedEventArgs(
                        exception.Credential.UserName,
                        exception.Credential.Password);

                    this.OnAuthenticationFailed(args);

                    retry = !args.Cancel;

                    credential = IsValid(args.NewCredential)
                        ? args.NewCredential
                        : new NetworkCredential(args.UserName, args.Password);
                }
            }

            if (!string.IsNullOrWhiteSpace(atomFeed))
            {
                bool isModified = true;

                StringReader stringReader = new StringReader(atomFeed);

                XDocument document = XDocument.Load(stringReader);

                int unreadCount = XHelper.GetElementValue<int>(document, "fullcount");

                DateTime modificationDate = XHelper.GetElementValue<DateTime>(document, "modified");

                if (this._lastModifiedDates.ContainsKey(credential.UserName))
                {
                    DateTime lastModifiedDate = this._lastModifiedDates[credential.UserName];

                    isModified = (lastModifiedDate < modificationDate);
                }

                this._lastModifiedDates[credential.UserName] = modificationDate;

                status = new AccountStatus(isModified)
                {
                    ModificationDate = modificationDate,
                    UnreadCount = unreadCount,
                };
            }

            return status;
        }

        /// <summary>
        /// Raises the <see cref="GmailManager.AuthenticationFailed"/> event.
        /// </summary>
        /// <param name="e">The arguments for the event.</param>
        protected virtual void OnAuthenticationFailed(AuthenticationFailedEventArgs e)
        {
            if (this.AuthenticationFailed != null)
            {
                this.AuthenticationFailed(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="GmailManager.AuthenticationSucceeded"/> event.
        /// </summary>
        /// <param name="e">The arguments for the event.</param>
        protected virtual void OnAuthenticationSucceeded(AuthenticationSucceededEventArgs e)
        {
            if (this.AuthenticationSucceeded != null)
            {
                this.AuthenticationSucceeded(this, e);
            }
        }

        private static bool IsValid(NetworkCredential credential)
        {
            return credential != null
                && !string.IsNullOrWhiteSpace(credential.UserName)
                && (!string.IsNullOrWhiteSpace(credential.Password)
                    || (credential.SecurePassword != null
                        && credential.SecurePassword.Length > 0));
        }
    }
}
